package com.tiejun.ge.common.core.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @program: springboot
 * @description: HttpUtils
 * @author: getiejun
 * @create: 2025-10-19 23:33
 **/
@Slf4j
public class HttpUtils {
    private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    // 默认连接超时时间(毫秒)
    private static final int DEFAULT_CONNECT_TIMEOUT = 5000;
    // 默认读取超时时间(毫秒)
    private static final int DEFAULT_READ_TIMEOUT = 5000;
    // 默认字符编码
    private static final String DEFAULT_CHARSET = StandardCharsets.UTF_8.name();
    // 默认重试次数
    private static final int DEFAULT_RETRY_TIMES = 2;
    // 重试间隔时间(毫秒)
    private static final long RETRY_INTERVAL = 1000;

    /**
     * 发送GET请求（带重试机制）
     */
    public static String doGet(String url) throws HttpException {
        return doGet(url, null, null, DEFAULT_RETRY_TIMES);
    }

    public static String doGet(String url, Map<String, String> params, Map<String, String> headers, int retryTimes) throws HttpException {
        // 拼接参数
        if (params != null && !params.isEmpty()) {
            StringBuilder urlBuilder = new StringBuilder(url);
            boolean isFirstParam = url.indexOf('?') == -1;
            for (Map.Entry<String, String> entry : params.entrySet()) {
                urlBuilder.append(isFirstParam ? "?" : "&")
                        .append(entry.getKey())
                        .append("=")
                        .append(entry.getValue());
                isFirstParam = false;
            }
            url = urlBuilder.toString();
        }

        HttpGet httpGet = new HttpGet(url);
        setRequestConfig(httpGet);
        setHeaders(httpGet, headers);

        return executeWithRetry(httpGet, retryTimes);
    }

    /**
     * 发送POST表单请求（带重试机制）
     */
    public static String doPostForm(String url, Map<String, String> params) throws HttpException {
        return doPostForm(url, params, null, DEFAULT_RETRY_TIMES);
    }

    public static String doPostForm(String url, Map<String, String> params, Map<String, String> headers, int retryTimes) throws HttpException {
        HttpPost httpPost = new HttpPost(url);
        setRequestConfig(httpPost);
        setHeaders(httpPost, headers);

        if (params != null && !params.isEmpty()) {
            List<NameValuePair> formParams = new ArrayList<>();
            for (Map.Entry<String, String> entry : params.entrySet()) {
                formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(formParams, DEFAULT_CHARSET));
            } catch (Exception e) {
                throw new HttpException("构建表单参数失败", e);
            }
        }

        return executeWithRetry(httpPost, retryTimes);
    }

    /**
     * 发送POST JSON请求（带重试机制）
     */
    public static String doPostJson(String url, String json) throws HttpException {
        return doPostJson(url, json, null, DEFAULT_RETRY_TIMES);
    }

    public static String doPostJson(String url, String json, Map<String, String> headers, int retryTimes) throws HttpException {
        HttpPost httpPost = new HttpPost(url);
        setRequestConfig(httpPost);

        // 设置默认Content-Type
        if (headers == null || !headers.containsKey("Content-Type")) {
            httpPost.setHeader("Content-Type", "application/json;charset=" + DEFAULT_CHARSET);
        }
        setHeaders(httpPost, headers);

        if (json != null && !json.isEmpty()) {
            StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(stringEntity);
        }

        return executeWithRetry(httpPost, retryTimes);
    }

    /**
     * 发送PUT请求（带重试机制）
     */
    public static String doPut(String url, String json) throws HttpException {
        return doPut(url, json, null, DEFAULT_RETRY_TIMES);
    }

    public static String doPut(String url, String json, Map<String, String> headers, int retryTimes) throws HttpException {
        HttpPut httpPut = new HttpPut(url);
        setRequestConfig(httpPut);

        if (headers == null || !headers.containsKey("Content-Type")) {
            httpPut.setHeader("Content-Type", "application/json;charset=" + DEFAULT_CHARSET);
        }
        setHeaders(httpPut, headers);

        if (json != null && !json.isEmpty()) {
            StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPut.setEntity(stringEntity);
        }

        return executeWithRetry(httpPut, retryTimes);
    }

    /**
     * 发送DELETE请求（带重试机制）
     */
    public static String doDelete(String url) throws HttpException {
        return doDelete(url, null, DEFAULT_RETRY_TIMES);
    }

    public static String doDelete(String url, Map<String, String> headers, int retryTimes) throws HttpException {
        HttpDelete httpDelete = new HttpDelete(url);
        setRequestConfig(httpDelete);
        setHeaders(httpDelete, headers);

        return executeWithRetry(httpDelete, retryTimes);
    }

    /**
     * 带重试机制的请求执行器
     * @param request HTTP请求对象
     * @param retryTimes 重试次数（0表示不重试）
     * @return 响应结果
     * @throws HttpException 封装后的HTTP异常
     */
    private static String executeWithRetry(HttpRequestBase request, int retryTimes) throws HttpException {
        int currentRetry = 0;
        while (true) {
            try {
                return executeRequest(request);
            } catch (HttpException e) {
                // 如果是可重试异常且未达到最大重试次数，则进行重试
                if (isRetryable(e) && currentRetry < retryTimes) {
                    currentRetry++;
                    logger.warn("请求[{}]执行失败，准备进行第{}次重试，原因：{}",
                            request.getURI(), currentRetry, e.getMessage());
                    try {
                        TimeUnit.MILLISECONDS.sleep(RETRY_INTERVAL);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new HttpException("重试等待被中断", ie);
                    }
                } else {
                    // 不可重试或达到最大重试次数，抛出异常
                    throw new HttpException("请求[" + request.getURI() + "]执行失败，已达到最大重试次数(" + retryTimes + ")", e);
                }
            }
        }
    }

    /**
     * 执行单次请求
     */
    private static String executeRequest(HttpRequestBase request) throws HttpException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(request)) {

            int statusCode = response.getStatusLine().getStatusCode();
            HttpEntity entity = response.getEntity();
            String responseBody = entity != null ? EntityUtils.toString(entity, DEFAULT_CHARSET) : null;

            // 验证状态码（2xx表示成功）
            if (statusCode < 200 || statusCode >= 300) {
                logger.error("请求[{}]失败，状态码：{}，响应内容：{}",
                        request.getURI(), statusCode, responseBody);
                throw new HttpException("HTTP请求失败，状态码：" + statusCode,
                        statusCode, responseBody);
            }

            logger.debug("请求[{}]成功，状态码：{}，响应内容：{}",
                    request.getURI(), statusCode, responseBody);
            return responseBody;

        } catch (IOException e) {
            logger.error("请求[{}]发生IO异常", request.getURI(), e);
            // IO异常属于可重试异常
            throw new HttpException("HTTP请求IO异常：" + e.getMessage(), e, true);
        } finally {
            request.releaseConnection();
        }
    }

    /**
     * 判断异常是否可重试
     */
    private static boolean isRetryable(HttpException e) {
        return e.isRetryable();
    }

    /**
     * 设置请求配置
     */
    private static void setRequestConfig(HttpRequestBase request) {
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
                .setConnectionRequestTimeout(DEFAULT_CONNECT_TIMEOUT)
                .setSocketTimeout(DEFAULT_READ_TIMEOUT)
                .build();
        request.setConfig(config);
    }

    /**
     * 设置请求头
     */
    private static void setHeaders(HttpRequestBase request, Map<String, String> headers) {
        if (headers != null && !headers.isEmpty()) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                request.setHeader(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * 自定义HTTP异常类（包含状态码、响应体和是否可重试标记）
     */
    public static class HttpException extends Exception {
        private final int statusCode; // 响应状态码（-1表示非HTTP状态码异常）
        private final String responseBody; // 响应体内容
        private final boolean retryable; // 是否可重试

        public HttpException(String message) {
            this(message, -1, null, false);
        }

        public HttpException(String message, Throwable cause) {
            this(message, -1, null, false, cause);
        }

        public HttpException(String message, int statusCode, String responseBody) {
            this(message, statusCode, responseBody, false);
        }

        public HttpException(String message, Throwable cause, boolean retryable) {
            this(message, -1, null, retryable, cause);
        }

        public HttpException(String message, int statusCode, String responseBody, boolean retryable) {
            super(message);
            this.statusCode = statusCode;
            this.responseBody = responseBody;
            this.retryable = retryable;
        }

        public HttpException(String message, int statusCode, String responseBody, boolean retryable, Throwable cause) {
            super(message, cause);
            this.statusCode = statusCode;
            this.responseBody = responseBody;
            this.retryable = retryable;
        }

        public int getStatusCode() {
            return statusCode;
        }

        public String getResponseBody() {
            return responseBody;
        }

        public boolean isRetryable() {
            return retryable;
        }
    }
}
